﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using Kyoh.Lib.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace Kyoh.App.Renamer
{
    public sealed class RenameOptionCollection : List<RenameOption>, IEquatable<RenameOptionCollection>, IEqualityComparer<RenameOption>
    {
        private Wildcard _wildcard;
        public string Path { get; private set; }
        public bool ContainsDirectory { get; private set; }
        public bool ContainsFile { get; private set; }
        public int Depth { get; private set; }
        public string Filter { get; private set; }
        public bool IgnoreCase { get; private set; }

        private RenameOptionCollection() { IgnoreCase = true; }
        public RenameOptionCollection(string path, string filter, bool ignoreCase, bool directory, bool file, int depth)
        {
            ApplyFilter(path, filter, ignoreCase, directory, file, depth);
        }

        public static readonly XNamespace rn = "http://kyoh.net/Renamer/2012";
        public static RenameOptionCollection Deserialize(Stream stream)
        {
            using (var reader = new StreamReader(stream))
                return Deserialize(reader);
        }

        public static RenameOptionCollection Deserialize(TextReader textReader)
        {
            var settings = new XmlReaderSettings();
            settings.Schemas.Add(GetSchema());
            settings.ValidationType = ValidationType.Schema;
            using (var xmlReader = XmlReader.Create(textReader, settings))
            {
                var doc = XDocument.Load(xmlReader);
                var root = doc.Element(rn + "batch");
                var version = TryGetValue<string>(root, "version", "1.0.0.0");
                var source = root.Element(rn + "source");
                var filter = source.Element(rn + "filter");
                var batch = new RenameOptionCollection(
                    GetValue(source, "path"),
                        filter.Element(rn + "search").Value,
                        TryGetValue<bool>(filter, "ignoreCase", false),
                    GetValue<bool>(source, "directory"),
                    GetValue<bool>(source, "file"),
                    GetValue<int>(source, "depth"));

                foreach (var ren in root.Elements(rn + "rename"))
                {
                    var num = ren.Element(rn + "number");
                    var pat = ren.Element(rn + "pattern");

                    batch.Add(new RenameOption(
                        GetValue<bool>(num, "grouped"),
                        GetValue<int>(num, "start"),
                        GetValue<int>(num, "step"),
                        GetValue<NumberStyle>(num, "style"),
                        (PatternMethod)Enum.Parse(typeof(PatternMethod), pat.Attribute(rn + "method").Value),
                        GetValue(pat, "search"),
                        GetValue(pat, "replace"),
                        TryGetValue<bool>(pat, "ignoreCase", false),
                        TryGetValue<bool>(pat, "except", false)));
                }
                return batch;
            }
        }

        /// <summary>
        /// XmlSchemaクラスオブジェクトの取得(リソースxsdファイルから取得)
        /// </summary>
        /// <param name="resourceName">リソース名</param>
        /// <returns>XmlSchemaクラスオブジェクト</returns>
        private static XmlSchema GetSchema()
        {
            XmlSchema schema;
            var thisAssembly = Assembly.GetExecutingAssembly();
            using (Stream resourceStream = thisAssembly.GetManifestResourceStream("Kyoh.App.Renamer.batch.xsd"))
                schema = XmlSchema.Read(resourceStream, null);
            return schema;
        }

        private static string GetValue(XElement parent, string elementName)
        {
            return parent.Element(rn + elementName).Value;
        }

        private static T GetValue<T>(XElement parent, string elementName)
        {
            if (typeof(T).IsEnum)
                return (T)Enum.Parse(typeof(T), parent.Element(rn + elementName).Value);
            else
                return (T)Convert.ChangeType(parent.Element(rn + elementName).Value, typeof(T));
        }

        private static T TryGetValue<T>(XElement parent, string elementName, T defaultValue)
        {
            var child = parent.Elements(rn + elementName).FirstOrDefault();
            if (child == null)
                return defaultValue;
            else
            {
                if (typeof(T).IsEnum)
                    return (T)Enum.Parse(typeof(T), child.Value);
                else
                    return (T)Convert.ChangeType(child.Value, typeof(T));
            }
        }

        public static void Serialize(Stream stream, RenameOptionCollection batch)
        {
            using (var writer = new StreamWriter(stream))
                Serialize(writer, batch);
        }

        public static void Serialize(TextWriter textWriter, RenameOptionCollection batch)
        {
            var first = batch.FirstOrDefault();
            if(first == null)
                return;
            var root = new XElement(rn + "batch",
                new XAttribute(rn + "version", Application.ProductVersion),
                new XElement(rn + "source",
                    new XElement(rn + "path", batch.Path),
                    new XElement(rn + "depth", batch.Depth),
                    new XElement(rn + "directory", batch.ContainsDirectory),
                    new XElement(rn + "file", batch.ContainsFile),
                    new XElement(rn + "filter",
                        new XAttribute(rn + "method", PatternMethod.Wildcard.ToString()),
                        new XElement(rn + "search", batch.Filter),
                        new XElement(rn + "ignoreCase", batch.IgnoreCase))));
            foreach(var ren in batch)
            {
                var rename =
                    new XElement(rn + "rename",
                        new XElement(rn + "number",
                            new XElement(rn + "style", ren.Style),
                            new XElement(rn + "grouped", ren.Grouped),
                            new XElement(rn + "start", ren.Start),
                            new XElement(rn + "step", ren.Step)),
                        new XElement(rn + "pattern",
                            new XAttribute(rn + "method", ren.Method),
                            new XElement(rn + "search", ren.Pattern),
                            new XElement(rn + "replace", ren.Replacement),
                            new XElement(rn + "ignoreCase", ren.IgnoreCase),
                            new XElement(rn + "except", ren.Except))
                        );
                root.Add(rename);
            }

            var doc = new XDocument(root);
            var settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.IndentChars=" ";
            settings.NewLineOnAttributes = false;
            settings.OmitXmlDeclaration = true;
            using (var writer = XmlWriter.Create(textWriter, settings))
                doc.WriteTo(writer);
        }

        public bool ApplyFilter(string path, string filter, bool ignoreCase, bool directory, bool file, int depth)
        {
            bool changed = false;
            if (Filter != filter)
            {
                Filter = filter;
                changed = true;
            }
            if (IgnoreCase != ignoreCase)
            {
                IgnoreCase = ignoreCase;
                changed = true;
            }
            
            if (changed || _wildcard == null)
                _wildcard = new Wildcard(filter, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);

            if (Path != path)
            {
                Path = path;
                changed = true;
            }
            if (ContainsDirectory != directory)
            {
                ContainsDirectory = directory;
                changed = true;
            }
            if (ContainsFile != file)
            {
                ContainsFile = file;
                changed = true;
            }
            if (Depth != depth)
            {
                Depth = depth;
                changed = true;
            }
            return changed;
        }

        public bool ApplyOption(int optionIndex, bool grouped, int start, int step, NumberStyle style,
            PatternMethod method, string search, string replace, bool ignoreCase, bool except)
        {
            return this[optionIndex].ApplyOption(grouped, start, step, style, method, search, replace, ignoreCase, except);
        }

        public void ResetNumbers()
        {
            foreach (var o in this)
                o.Reset();
        }

        public bool IsMatch(string input)
        {
            return _wildcard.IsMatch(input);
        }

        public bool Equals(RenameOptionCollection other)
        {
            return Path == other.Path
                && ContainsDirectory == other.ContainsDirectory
                && ContainsFile == other.ContainsFile
                && Depth == other.Depth
                && Filter == other.Filter
                && IgnoreCase == other.IgnoreCase
                && Count == other.Count
                && Enumerable.SequenceEqual(this, other, this);
        }

        public bool Equals(RenameOption x, RenameOption y)
        {
            return x.Grouped == y.Grouped
                && x.Start == y.Start
                && x.Step == y.Step
                && x.Style == y.Style
                && x.Method == y.Method
                && x.IgnoreCase == y.IgnoreCase
                && x.Pattern == y.Pattern
                && x.Replacement == y.Replacement;
        }

        public int GetHashCode(RenameOption obj)
        {
            return obj == null
                ? 0
                : obj.Grouped.GetHashCode()
                    * obj.Start.GetHashCode()
                    * obj.Step.GetHashCode()
                    * obj.Style.GetHashCode()
                    * obj.Method.GetHashCode()
                    * obj.IgnoreCase.GetHashCode()
                    * (obj.Pattern == null ? 1 : obj.Pattern.GetHashCode())
                    * (obj.Replacement == null ? 1 : obj.Replacement.GetHashCode());
        }
    }
}